use super::{
    cmd::cache, json, work_dir, Client, JsonFile, MapSv, RNull, Resp, SvCache, VBase, VMap, Value,
};
use base::{pre_cur_time, utc_offset_from_zero, SS};
use std::time::Duration;
use tokio::time::sleep;
use tracing::{instrument, warn};

#[instrument(skip_all, name = "do_custom")]
pub async fn do_custom(cache: SvCache, client: &Client) {
    let mut setting = match load_setting() {
        Ok(v) => v,
        Err(_) => MapSv::new(),
    };

    warn!("setting: {:?}", &setting);

    loop {
        if let Err(e) = do_count(&mut setting, &cache, client).await {
            warn!("count error: {:?}", e);
        }

        sleep(Duration::from_millis(1000)).await;
    }
}

#[instrument(skip_all, name = "do_count")]
async fn do_count(setting: &mut MapSv, cache: &SvCache, client: &Client) -> Result<(), Value> {
    let now = base::cur_time_timestamp();

    if let None = is_hit("count", setting, now) {
        return Ok(());
    }

    let v = cache::CfgCache;
    let cmd = json!({ "mod": "it" });

    let body = v.do_cmd(&cmd, "list", cache.clone()).await?;
    let body = body.ck_array("data")?;

    let mut tab = Vec::new();

    for d in body.iter() {
        let d = d.ckobj()?;

        let db_save = match d.k_bool("db_save") {
            Some(v) => v,
            None => false,
        };

        if !db_save {
            break;
        }

        let uid = d.ck_str("uid")?;
        tab.push(uid);
    }

    if tab.is_empty() {
        return Ok(());
    }

    warn!("tab: {:?}", &tab);

    let mut ret = Vec::new();
    let mut cfg = SS::new();

    let _ = get_cfg("pick", &mut cfg);

    warn!("cfg: {:?}", &cfg);

    for d in tab.iter() {
        if let Ok(body) = read_data(d, &cfg, client).await {
            let body = body.ck_obj("data")?.ck_obj(d)?;

            let end = body.ck_str("end")?;
            let data = body.ck_array("data")?;

            cfg.insert(d.to_string(), end.to_string());

            for it in data.iter() {
                let time = it.ck_str("time")?;
                let wsn = it.ck_str("wsn")?;
                let num = it.ck_f64("num")?;

                ret.push(json!({
                    "time": time,
                    "wsn": wsn,
                    "v": {
                        "num": num,
                    }
                }));
            }
        }
    }

    let cmd = json!({
        "db": "pick",
        "tab": "count",
        "line": "1",
        "que": false,
        "data": ret,
    });

    client.timedb_ip(&cmd, "chunk", "192.168.50.130").await?;

    let _ = save_cfg("pick", &cfg);

    Ok(())
}

fn is_hit(id: &str, setting: &mut MapSv, now: i64) -> Option<()> {
    let scan_time = match setting.k_i64(&format!("scan{id}")) {
        Some(v) => v,
        None => 1000 * 60 * 5, // 5 minutes
    };

    let cur_time = format!("time{id}");

    match setting.k_i64(&cur_time) {
        Some(cur) => {
            let sub = now - cur;

            if sub >= scan_time {
                setting.insert(cur_time, json!(now));
                return Some(());
            }
        }
        None => {
            setting.insert(cur_time, json!(now));
            return Some(());
        }
    }

    None
}

fn get_cfg(fname: &str, cfg: &mut SS) -> RNull {
    let r = work_dir(fname);

    if let Ok(body) = JsonFile::read_file(&r) {
        let body = body.ckobj()?;

        for (k, v) in body.iter() {
            let v = v.ckstr()?;
            cfg.insert(k.to_string(), v.to_string());
        }
    }

    Ok(())
}

fn save_cfg(fname: &str, cfg: &SS) -> RNull {
    let r = work_dir(fname);

    let val = json!(cfg);
    JsonFile::write_file(&r, &val)?;

    Ok(())
}

async fn read_data(tab: &str, cfg: &SS, client: &Client) -> Resp {
    let now = pre_cur_time(1000 * 60 * 5, "%Y-%m-%dT%H:%M:%SZ")?;
    let utc = utc_offset_from_zero();

    let mut opt = vec![json!({"le": "time", "v": now, "utc": utc})];

    if let Some(t) = cfg.k_str(tab) {
        opt.push(json!({"gt": "time", "v": t}));
    }

    let cmd = json!({
        "n": tab,
        "obj": "*",
        "opt": {
            "and": opt
        }
    });

    client.timedb(&cmd, "get").await
}

fn load_setting() -> Result<MapSv, Value> {
    let file = work_dir("cfg.json");

    let body = JsonFile::read_file(&file)?;
    let body = body.ckobj()?;

    Ok(body.clone())
}
